<?php

require_once dirname(__FILE__)."/parse/parser.so.php";

/*
known brokenness:
-- no unicode support. PHP has its share of blame for this.
-- line terminators allowed in string literals. mostly because I like it.
-- no magic semi-colons. maybe later.
-- no /regexp/ literals. RegExp() objects are not implemented either, but stubs are there.
-- various deviations from a "pure" ecma-262 grammar. hopefully nobody will notice.
-- various shortcuts in standard functions implementations.
-- slow. The lexer has some retarded matching logic, and the runtime is object-happy.
-- no eval(), no Function(). You don't really need those things anyway.
-- using exceptions can crash php. That will stop once I rewrite jsrt:: to avoid call_user_func().
*/


class jsc {

function gensym($prefix='') {
  static $uniq = 0;
  return $prefix.++$uniq;
}

static function compile($codestr) {
  #-- ideally, we want to avoid generating our parser table at every compilation.
  #-- 2 layers of caching: using an external file, and using static vars.
  global $def_fun_track;
  static $lex = NULL;
  static $parser = NULL;
  if ($lex == NULL) {
    $t0 = microtime(1);
    $path = dirname(__FILE__)."/js.ly.php";
    if (file_exists($path)) {
      include $path;
    } else {
      $path = JS_CACHE_DIR."/js.ly.php";    
      if (file_exists($path)) {
        include $path;
      } else {
        require_once dirname(__FILE__)."/parse/generator.so.php";
        $lexp = generate_scanner_from_file(dirname(__FILE__)."/js.l", 0)->pattern;
        $dpa = generate_parser_from_file(dirname(__FILE__)."/js.y");
        file_put_contents($path, "<?php\n".
        "// This file is dynamically generated from js.l and js.y by metaphp's CFG parser\n".
        "// Do not waste your time editing it or reading it. Move along. Thank you.\n".
        "\n".$GLOBALS['func_def']."\$lexp = ".var_export($lexp,1).";\n\$dpa = ".var_export($dpa,1).";\n?>");
      }
    }
    $t1 = microtime(1);
    $lex = new preg_scanner(0, $lexp);
    $parser = new easy_parser($dpa);
    $t2 = microtime(1);
    #echo "Table loading: ".($t1-$t0)." seconds<br>";
    #echo "Pre generation: ".($t2-$t1)." seconds<br>";
  }
  $t3 = microtime(1);
  $lex->start($codestr);
  $program = $parser->parse("Program", $lex);
  $t4 = microtime(1);
  #echo "Parse time: ".($t4-$t3)." seconds<br>";
  
  # convert into usable php code
  try {
    $php = $program->emit();
  } catch (Exception $e) {
    #-- Compilation error. should be pretty rare. usually the parser will barf long before this.
    echo "Compilation Error: ".$e->value->msg."<hr>";
  }
  return $php;
}

}

/*
This seems somehow a bit better than having monolithic sequential modules.
*/
abstract class js_construct {
  abstract function emit($w=0);
}
class js_program extends js_construct {
  static $source;
  function __construct($obj) {
    $this->src= $obj;
    self::$source = $obj;
  }
  function emit($w=0) {
    $source = $this->src->emit(1);
  
    $o = "/* Code generated by J4P5, the javascript interpreter for PHP5\n";
    $o.= " * http://j4p5.sf.net/ copyright(c) 2005 Henri T. <metal_hurlant@users.sourceforge.net>\n";
    $o.= " */\n\n";    
    $o.= "js::init();\n";
    $o.= $source;
    return $o;
  }
}
class js_source extends js_construct {
  function __construct($statements=array(), $functions=array()) {
    $this->code = $statements;
    $this->functions = $functions;
    $this->vars = array();
    $this->funcdef = array(); // only used by toplevel object
  }
  function addStatement($statement) {
    $this->code[]=$statement;
  }
  function addFunction($function) {
    $this->functions[] = $function;
  }
  static public $that;
  static public $nest;
  static public $labels;
  function addVariable($var) {
    js_source::$that->vars[] = $var;
  }
  function addFunctionExpression($function) {
    js_source::$that->functions[] = $function;
  }
  function addFunctionDefinition($function) {
    js_program::$source->funcdef[] = $function;
  }
  function emit($w=0) {
    self::$nest = 0;
    self::$labels = array();
    $o = '';    
    #dump the main body
    $saved_that = js_source::$that;
    js_source::$that = $this;
    $s = '';
    foreach ($this->code as $statement) {
      $s .= $statement->emit(1);
    }
    js_source::$that = $saved_that;
    #dump variable declarations now that we went through the body
    $v = js_var::really_emit($this->vars);
    #dump function expressions.
    $f = '';
    foreach ($this->functions as $function) {
      $f .= $function->function_emit();
    }
    if ($f!='') $f = "/* function mapping */\n".$f;
    #if toplevel, dump function declarations
    $fd = "";
    if ($this === js_program::$source) {
      $fd = '';
      foreach ($this->funcdef as $function) {
        $fd .= $function->toplevel_emit();
      }
      if ($fd!='') $fd = "/* function declarations */\n".$fd;
    }
    # that's all folks
    return $fd.$f.$v.$s;
  }
}
class js_print extends js_construct {
  function __construct() {
    $this->args = func_get_args();
  }
  function emit($w=0) {
    $o='jsrt::write( ';
    $first=true;
    foreach ($this->args as $arg) {
      if ($first) {$first^=true;} else {$o.=",";}
      $o.="(".(get_class($arg)?$arg->emit(1):$arg).")";
    }
    $o.= ");\n";
    return $o;
  }
}
class js_function_definition extends js_construct {
  static $in_function = 0;
  function __construct($a) {
    list($this->id, $this->params, $this->body) = $a;
    $this->phpid = jsc::gensym("jsrt_uf");
  }
  function toplevel_emit() {
    $o  = "function ".$this->phpid."() {\n";
    $o .= "  ".trim(str_replace("\n", "\n  ", $this->body));
    $o .= "\n}\n";
    return $o;  
  }
  function function_emit() {
    self::$in_function++;
    $this->body = $this->body->emit(1); // do it early, to catch inner functions
    self::$in_function--;
    js_source::addFunctionDefinition($this);
    $id= "";
    if (true or $this->id!='') {
      $id = ",'".$this->id."'";
    }
    $p = "";
    if (count($this->params)>0) {
      $p = ",array('".implode("','",$this->params)."')";
    }
    return "jsrt::define_function('".$this->phpid."'".$id.$p.");\n";
  }
  function emit($w=0) {
    #-- if this gets called, we're a function inside an expression.
    js_source::addFunctionExpression($this);
    #-- XXX output something that will return a handle to the function.
    return "jsrt::function_id('".$this->phpid."')";
  }
}
abstract class js_unary_op extends js_construct {
  function __construct($a,$w=0) {
    $this->arg = $a[0];
    $this->wantValue = $w;
    $this->jsrt_op = substr(get_class($this), 3);
  }
  function emit($w=0) {
    return "jsrt::expr_".$this->jsrt_op."(".$this->arg->emit($this->wantValue).")";
  }
}
abstract class js_binary_op extends js_construct {
  function __construct($a,$w1=0,$w2=0) {
    $this->arg1 = $a[0];
    $this->arg2 = $a[1];
    $this->wantValue1 = $w1;
    $this->wantValue2 = $w2;
    $this->jsrt_op = substr(get_class($this), 3);
  }
  function emit($w=0) {
    return "jsrt::expr_".$this->jsrt_op."(".$this->arg1->emit($this->wantValue1).",".$this->arg2->emit($this->wantValue2).")";
  }
}
class js_ternary extends js_construct {
  function __construct() {
    $this->args = func_get_args();
    $this->jsrt_op = substr(get_class($this), 3);
  }
  function emit($w=0) {
    #-- can't use a helper function to maintain the short-circuit thing.
    return "(js_bool(".$this->args[0]->emit(1).")?(".$this->args[1]->emit(1)."):(".$this->args[2]->emit(1)."))";
  }
}
class js_post_pp extends js_unary_op {
  function __construct() {parent::__construct(func_get_args());}
}
class js_post_mm extends js_unary_op {
  function __construct() {parent::__construct(func_get_args());}
}
class js_delete extends js_unary_op {
  function __construct() {parent::__construct(func_get_args());}
}
class js_void extends js_unary_op {
  function __construct() {parent::__construct(func_get_args(),1);}
}
class js_typeof extends js_unary_op {
  function __construct() {parent::__construct(func_get_args(),1);}
}
class js_pre_pp extends js_unary_op {
  function __construct() {parent::__construct(func_get_args());}
}
class js_pre_mm extends js_unary_op {
  function __construct() {parent::__construct(func_get_args());}
}
class js_u_plus extends js_unary_op {
  function __construct() {parent::__construct(func_get_args(),1);}
}
class js_u_minus extends js_unary_op {
  function __construct() {parent::__construct(func_get_args(),1);}
}
class js_bit_not extends js_unary_op {
  function __construct() {parent::__construct(func_get_args(),1);}
}
class js_not extends js_unary_op {
  function __construct() {parent::__construct(func_get_args(),1);}
}
class js_multiply extends js_binary_op {
  function __construct() {parent::__construct(func_get_args(),1,1);}
}
class js_divide extends js_binary_op {
  function __construct() {parent::__construct(func_get_args(),1,1);}
}
class js_modulo extends js_binary_op {
  function __construct() {parent::__construct(func_get_args(),1,1);}
}
class js_plus extends js_binary_op {
  function __construct() {parent::__construct(func_get_args(),1,1);}
}
class js_minus extends js_binary_op {
  function __construct() {parent::__construct(func_get_args(),1,1);}
}
class js_lsh extends js_binary_op {
  function __construct() {parent::__construct(func_get_args(),1,1);}
}
class js_rsh extends js_binary_op {
  function __construct() {parent::__construct(func_get_args(),1,1);}
}
class js_ursh extends js_binary_op {
  function __construct() {parent::__construct(func_get_args(),1,1);}
}
class js_lt extends js_binary_op {
  function __construct() {parent::__construct(func_get_args(),1,1);}
  function emit($w=0) {
    // weak attempt at speeding things. probably not worth it.
    return "jsrt::cmp(".$this->arg1->emit(1).",".$this->arg2->emit(1).", 1)";
  }
}
class js_gt extends js_binary_op {
  function __construct() {parent::__construct(func_get_args(),1,1);}
}
class js_lte extends js_binary_op {
  function __construct() {parent::__construct(func_get_args(),1,1);}
}
class js_gte extends js_binary_op {
  function __construct() {parent::__construct(func_get_args(),1,1);}
}
class js_instanceof extends js_binary_op {
  function __construct() {parent::__construct(func_get_args(),1,1);}
}
class js_in extends js_binary_op {
  function __construct() {parent::__construct(func_get_args(),1,1);}
}
class js_equal extends js_binary_op {
  function __construct() {parent::__construct(func_get_args(),1,1);}
}
class js_not_equal extends js_binary_op {
  function __construct() {parent::__construct(func_get_args(),1,1);}
}
class js_strict_equal extends js_binary_op {
  function __construct() {parent::__construct(func_get_args(),1,1);}
}
class js_strict_not_equal extends js_binary_op {
  function __construct() {parent::__construct(func_get_args(),1,1);}
}
class js_bit_and extends js_binary_op {
  function __construct() {parent::__construct(func_get_args(),1,1);}
}
class js_bit_xor extends js_binary_op {
  function __construct() {parent::__construct(func_get_args(),1,1);}
}
class js_bit_or extends js_binary_op {
  function __construct() {parent::__construct(func_get_args(),1,1);}
}
class js_and extends js_binary_op {
  function __construct() {parent::__construct(func_get_args(),1,1);}
  #-- using plain functions would prevent short-circuiting
  function emit($w=0) {
    $tmp=jsc::gensym("sc");
    return "(!js_bool(\$$tmp=".$this->arg1->emit(1).")?\$$tmp:".$this->arg2->emit(1).")";
  }
}
class js_or extends js_binary_op {
  function __construct() {parent::__construct(func_get_args());}
  #-- using plain functions would prevent short-circuiting
  function emit($w=0) {
    $tmp=jsc::gensym("sc");
    return "(js_bool(\$$tmp=".$this->arg1->emit(1).")?\$$tmp:".$this->arg2->emit(1).")";
  }
}
class js_assign extends js_binary_op {
  function __construct() {parent::__construct(func_get_args(),0,1);}
}
class js_compound_assign extends js_construct {
  function __construct() {
    list($this->a, $this->b, $this->op) = func_get_args();
  }
  function emit($w=0) {
    switch($this->op) {
      case '*=': $s="expr_multiply"; break;
      case '/=': $s="expr_divide"; break;
      case '%=': $s="expr_modulo"; break;
      case '+=': $s="expr_plus"; break;
      case '-=':  $s="expr_minus"; break;
      case '<<=': $s="expr_lsh"; break;
      case '>>=': $s="expr_rsh"; break;
      case '>>>=': $s="expr_ursh"; break;
      case '&=': $s="expr_bit_and"; break;
      case '^=': $s="expr_bit_xor"; break;
      case '|=': $s="expr_bit_or"; break;
    }
    return "jsrt::expr_assign(".$this->a->emit().",".$this->b->emit(1).",'".$s."')";
  }
}
class js_comma extends js_binary_op {
  function __construct() {parent::__construct(func_get_args(),1,1);}
}
class js_var extends js_construct {
  function __construct($args) {
    $this->vars = $args;
  }
  function emit($w=0) {
    $o = '';
    foreach ($this->vars as $var) {    
      list($id, $init) = $var;
      js_source::$that->addVariable($id);
      if (get_class($init)) {
        $obj = new js_assign(new js_identifier($id), $init);
        $o .= $obj->emit(1);
        $o .= ";\n";
      }
    }
    return $o;
  }
  function really_emit($arr) {
    if (count($arr)==0) return '';
    $l = "'".implode("','",array_unique($arr))."'";
    return "jsrt::define_variables($l);\n";
  }
  function emit_for() {
    $this->emit(1);
    return "jsrt::id('".$this->vars[0][0]."')";
  }
}
class js_nop extends js_construct {
  function emit($w=0) {
    return '';
  }
}
class js_statement extends js_construct {
  function __construct($child) {
    $this->child = $child;
  }
  function emit($w=0) {
    return $this->child->emit(1).";\n";
  }
}
class js_block extends js_construct {
  function __construct($a) {
    $this->statements = $a;
  }
  function emit($w=0) {
    $o = "{\n";
    foreach ($this->statements as $statement) {
      $o.= "  ".trim(str_replace("\n", "\n  ", $statement->emit(1)))."\n";
    }
    $o.= "}\n";
    return $o;
  }
}
class js_if extends js_construct {
  function __construct($cond, $ifblock, $elseblock=null) {
    $this->cond = $cond;
    $this->ifblock = $ifblock;
    $this->elseblock = $elseblock;
  }
  function emit($w=0) {
    $o = "if (js_bool(".$this->cond->emit(1).")) ".$this->ifblock->emit(1);
    if ($this->elseblock) {
      $o = rtrim($o) . " else ".$this->elseblock->emit(1)."\n";
    }
    return $o;
  }
}
class js_do extends js_construct {
  function __construct($expr, $statement) {
    $this->expr = $expr;
    $this->statement = $statement;
  }
  function emit($w=0) {
    js_source::$nest++;
    $o = "do ".rtrim($this->statement->emit(1))." while (js_bool(".$this->expr->emit(1)."));\n";
    js_source::$nest--;
    return $o;
  }
}
class js_while extends js_construct {
  function __construct($expr, $statement) {
    $this->expr = $expr;
    $this->statement = $statement;
  }
  function emit($w=0) {
    js_source::$nest++;
    $o = "while (js_bool(".$this->expr->emit(1).")) ".$this->statement->emit(1)."\n";
    js_source::$nest--;
    return $o;
  }
}
class js_for extends js_construct {
  function __construct($init, $cond, $incr, $statement) {
    list($this->init, $this->cond, $this->incr, $this->statement) = func_get_args();
  }
  function emit($w=0) {
    $o = $this->init?$this->init->emit(1):'';
    js_source::$nest++;
    $o.= "for (;".($this->cond?"js_bool(".$this->cond->emit(1).")":'');
    $o.= ";".($this->incr?$this->incr->emit(1):'').") {\n";
    $o.=$this->statement->emit(1);
    $o.="\n}\n";
    js_source::$nest--;
    return $o;
  }
}
class js_for_in extends js_construct {
  function __construct($one, $list, $statement) {
    list($this->one, $this->list, $this->statement) = func_get_args();
  }
  function emit($w=0) {
    $key=jsc::gensym("fv");
    js_source::$nest++;
    $o ="foreach (".$this->list->emit(1)." as \$$key) {\n";
    if (get_class($this->one)=="js_var") {
      $v = $this->one->emit_for();
    } else {
      $v = $this->one->emit();
    }
    $o.="  jsrt::expr_assign($v, js_str(\$$key));\n";
    $o.= "  ".trim(str_replace("\n", "\n  ", $this->statement->emit(1)))."\n";
    $o.="}";
    js_source::$nest--;
    return $o;
  }
}
class js_label extends js_construct {
  function __construct($label, $block) {
    list($this->label, $this->block) = func_get_args();
    $p = explode(':',$this->label);
    $this->label = $p[0];
  }
  function emit($w=0) {
    // associate this label with current $nest;
    js_source::$labels[$this->label] = js_source::$nest;
    
    //return "/* ".$this->label." */ ".$this->block->emit();
    return $this->block->emit(1);
  }
}
class js_continue extends js_construct {
  function __construct($label) {
    $this->label = $label;
  }
  function emit($w=0) {
    if (js_source::$nest==0) {
      return "ERROR: continue outside of a loop\n*************************\n\n";
    }
    if ($this->label !== ';') {
      $depth = js_source::$nest - js_source::$labels[$this->label];
      $o = "continue $depth;\n";
    } else {
      $o = "continue;\n";
    }
    return $o;
  }
}
class js_break extends js_construct {
  function __construct($label) {
    $this->label = $label;
  }
  function emit($w=0) {
    if (js_source::$nest==0) {
      return "ERROR: break outside of a loop\n*************************\n\n";
    }
    if ($this->label !== ';') {
      $depth = js_source::$nest - js_source::$labels[$this->label];
      $o = "break $depth;\n";
    } else {
      $o = "break;\n";
    }
    return $o;
  }
}
class js_return extends js_construct {
  function __construct($expr) {
    $this->expr = $expr;
  }
  function emit($w=0) {
    if (js_function_definition::$in_function==0) {
      throw new js_exception(new js_syntaxerror("invalid return"));
    }
    if ($this->expr == ';') {
      return 'return jsrt::$undefined;\n';
    } else {
      return "return ".$this->expr->emit(1).";\n";
    }
  }
}
class js_with extends js_construct {
  function __construct($expr, $statement) {
    list($this->expr, $this->statement) = func_get_args();
  }
  function emit($w=0) {
    $o = "jsrt::push_scope(js_obj(".$this->expr->emit(1)."));\n";
    $o.= $this->statement->emit(1);
    $o.= "jsrt::pop_scope();\n";
    return $o;
  }
}
class js_switch extends js_construct {
  function __construct($expr, $block) {
    list($this->expr, $this->block) = func_get_args();
  }
  function emit($w=0) {
    $e = jsc::gensym("jsrt_sw");
    js_source::$nest++;    
    $o  = "\$$e = ".$this->expr->emit(1).";\n";
    $o .= "switch (true) {\n";
    foreach ($this->block as $case) {
      $case->e = $e;
      $o .= $case->emit(1);
    }
    $o.="\n}\n";
    js_source::$nest--;
    return $o;
  }
}
class js_case extends js_construct {
  function __construct($expr, $code) {
    list($this->expr, $this->code) = func_get_args();
  }
  function emit($w=0) {
    if ($this->expr == 0) {
      $o = "  default:\n";
    } else {
      $o = "  case (js_bool(jsrt::expr_strict_equal(\$".$this->e.",".$this->expr->emit(1)."))):\n";
    }
    foreach ($this->code as $code) {
      $o .= "    ".trim(str_replace("\n", "\n    ", $code->emit(1)))."\n";
    }
    return $o;
  }
}
class js_throw extends js_construct {
  /* js exceptions are sufficiently different from php5 exceptions to make them un-leverage-able. */
  function __construct($expr) {
    $this->expr = $expr;
  }
  function emit($w=0) {
    //return "return new js_completion(".$this->expr->emit().");\n";
    return "throw new js_exception(".$this->expr->emit(1).");\n";
  }
}
class js_try extends js_construct {
  function __construct($code, $catch = NULL, $final = NULL) {
    list($this->body, $this->catch, $this->final) = func_get_args();
    $this->id_try = jsc::gensym("jsrt_try");
    $this->id_catch = jsc::gensym("jsrt_catch");
    $this->id_finally = jsc::gensym("jsrt_finally");
  }
  function toplevel_emit() {
    $o  = "function ".$this->id_try."() {\n";
    $o .= "  try ";
    $o .= trim(str_replace("\n", "\n  ", $this->body));    
    $o .= " catch (Exception \$e) {\n";
    $o .= "    jsrt::\$exception = \$e;\n";
    $o .= "  }\n";
    $o .= "  return NULL;\n";
    $o .= "}\n";
    if ($this->catch != NULL) {
      $o .= "function ".$this->id_catch."() {\n";
      $o .= "  ".trim(str_replace("\n", "\n  ", $this->catch));
      $o .= "\n  return NULL;\n";
      $o .= "}\n";
    }
    if ($this->final != NULL) {
      $o .= "function ".$this->id_finally."() {\n";
      $o .= "  ".trim(str_replace("\n", "\n  ", $this->final));
      $o .= "\n  return NULL;\n";
      $o .= "}\n";
    }
    return $o;
  }
  function emit($w=0) {
    // so we put catch() and finally blocks in functions to be able to pick if/when to evaluate them
    // it's not clear why try is in a function too at this point. consistency? yeah, weak.
    js_source::addFunctionDefinition($this);
    $id = ($this->catch!=NULL)?$this->catch->id:'';
    $this->body = $this->body->emit(1);
    if ($this->catch!=NULL) $this->catch = $this->catch->emit(1);
    if ($this->final!=NULL) $this->final = $this->final->emit(1);
    $ret = jsc::gensym("jsrt_ret");
    $tmp = jsc::gensym("jsrt_tmp");

    // try is on its own to work around a crash in my version of php5
    // apparently, php exceptions inside func_user_call()ed code are not all that stable just yet.
    // XXX note: the crash can still occur. still not entirely sure how it happens.
    // it feels like exceptions thrown from call_user_func-ed code corrupt some php internals, which
    // result in a possible crash at a later point in the program flow.
    $o  = "\$$tmp = ".$this->id_try."();\n";
    $o .= "\$$ret = jsrt::trycatch(\$$tmp, ";
    $o .= ($this->catch!=NULL?"'".$this->id_catch."'":"NULL").", ";
    $o .= ($this->final!=NULL?"'".$this->id_finally."'":"NULL");
    $o .= ($this->catch!=NULL?", '".$id."'":"").");\n";
    $o .= "if (\$$ret != NULL) return \$$ret;\n";
    return $o;
  }
}
class js_catch extends js_construct {
  function __construct($id, $code) {
    list($this->id, $this->code) = func_get_args();
  }
  function emit($w=0) {
    // this kind of code makes you wonder why this is even an object. absorb me. please. XXX
    return $this->code->emit(1);
  }
}
class js_this extends js_construct {
  function emit($w=0) {
    return "jsrt::this()"; // should this be a jsrt::$this instead?
  }
}
class js_identifier extends js_construct {
  function __construct($id) {
    $this->id = $id;
  }
  function emit($wantvalue=0) {
    $v = $wantvalue?"v":"";
    return "jsrt::id$v('".$this->id."')";
  }
}
class js_literal_array extends js_construct {
  function __construct($arr) {
    $this->arr = $arr;
  }
  function emit($w=0) {
    $a = array();
    for ($i=0;$i<count($this->arr);$i++) {
      if ($this->arr[$i]!=NULL) {
        $a[$i] = $this->arr[$i]->emit(1);
      }
    }
    if (count($this->arr)==1 and get_class($this->arr[0])=="js_literal_null") {
      $a = array();
    }

    return "jsrt::literal_array(".implode(",",$a).")";
  }
}
class js_literal_object extends js_construct {
  function __construct($o=array()) {
    $this->obj = $o;
  }
  function emit($w=0) {
    $a = array();
    for ($i=0;$i<count($this->obj);$i++) {
      $a[] = $this->obj[$i]->emit();
    }
    return "jsrt::literal_object(".implode(",",$a).")";
  }
}
class js_literal_null extends js_construct {
  function emit($w=0) {
    return 'jsrt::$null';
  }
}
class js_literal_boolean extends js_construct {
  function __construct($v) {
    $this->v = $v;
  }
  function emit($w=0) {
    return $this->v?'jsrt::$true':'jsrt::$false';
  }
}
class js_literal_number extends js_construct {
  function __construct($v) {
    $this->v = $v;
  }
  function emit($w=0) {
    return "js_int(".$this->v.")";
  }
}
class js_literal_string extends js_construct {

  function __construct($a, $stripquotes=1) {
    if ($stripquotes) {
      $a = substr($a, 1, strlen($a) - 2);
    }
    $this->str = $this->parse_string($a);
  }

 function parse_string($str) {
  $out = '';
  $mode = 0;
  foreach (str_split($str) as $c) {
    switch ($mode) {
      case 0:
        if ($c == '\\') {
          $mode = 1;
        } else {
          $out.=$c;
        }
        break;
      case 1:
        $mode = 0;
        switch ($c) {
          case "'": $out.="'"; break;
          case '"': $out.='"'; break;
          case '\\': $out.="\\"; break;
          case 'b': $out.=chr(8); break;
          case 'f': $out.=chr(12); break;
          case 'n': $out.=chr(10); break;
          case 'r': $out.=chr(13); break;
          case 't': $out.=chr(9); break;
          case 'v': $out.=chr(11); break;
          case '0': $out.=chr(0); break; // not quite right. \040 fails.
          case 'x': $mode = 2; $b=''; break;
          case 'u': $mode = 4; $b=''; break;
          default:
            $out.=$c; break;
        }
        break;
      case 2:
        $b.=$c;
        if (stripos("0123456789abcdef",$c)!==false) {
          $mode = 3;
        } else {
          $out.='x'.$b;
          $mode = 0;
        }
        break;
      case 3:
        $b.=$c;
        if (stripos("0123456789abcdef",$c)!==false) {
          $out.=chr(hexdec($b));
          $mode = 0;
        } else {
          $out.='x'.$b;
          $mode = 0;
        }
        break;
      case 4:
        $b.=$c;
        if (stripos("0123456789abcdef",$c)!==false) {
          $mode = 5;
        } else {
          $out.='u'.$b;
          $mode = 0;
        }
        break;
      case 5:
        $b.=$c;
        if (stripos("0123456789abcdef",$c)!==false) {
          $mode = 6;
        } else {
          $out.='u'.$b;
          $mode = 0;
        }
        break;
      case 6:
        $b.=$c;
        if (stripos("0123456789abcdef",$c)!==false) {
          $mode = 7;
        } else {
          $out.='u'.$b;
          $mode = 0;
        }
        break;
      case 7:
        $b.=$c;
        if (stripos("0123456789abcdef",$c)!==false) {
          $out.=chr(hexdec($b));
          $mode = 0;
        } else {
          $out.='u'.$b;
          $mode = 0;
        }
        break;
    }
  }
  return $out;
 }
  function emit($w=0) {
    return "js_str(".var_export($this->str,1).")";
  }
}
class js_accessor extends js_construct {
  function __construct($obj, $member, $resolve) {
    list($this->obj, $this->member, $this->resolve) = func_get_args();
  }
  function emit($wantvalue=0) {
    $v = $wantvalue?"v":"";
    return "jsrt::dot$v(".$this->obj->emit(1).",".$this->member->emit($this->resolve).")";
  }
}
class js_new extends js_construct {
  function __construct($expr) {
    list($this->expr) = func_get_args();
    #-- if direct child is a js_call object, vampirize it.
    if (get_class($this->expr)=="js_call") {
      $this->args = $this->expr->args;
      $this->expr = $this->expr->expr;
    } else {
      $this->args = array();
    }
  }
  function emit($w=0) {
    $args=array();
    foreach ($this->args as $arg) {
      $args[] = $arg->emit(1);
    }
    return "jsrt::_new(".$this->expr->emit(1).", array(".implode(",",$args) ."))";
  }
}
class js_call extends js_construct {
  function __construct($expr, $args) {
    list($this->expr, $this->args) = func_get_args();
  }
  function emit($w=0) {
    $args=array();
    foreach ($this->args as $arg) {
      $args[] = $arg->emit(1);
    }
    return "jsrt::call(".$this->expr->emit().", array(".implode(",",$args) ."))";
  }
}

/*
short list of speed optimizations:
- use native PHP boolean, number and string types
  -> convert $val->toType() into jsrt::toType($val)
- have specialized emitted code when operand type is known at compile time.
  -> ie:(a-b) always return a number, therefore in (a-b)*(c-d), "*" doesn't need to handle non-numbers
*/  